/*
Copyright (c) 2009-2010, Warwick Warp Limited
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the Warwick Warp Limited nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL WARWICK WARP LIMITED BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifndef WARRAY_WARRAY_ACCESSOR_H_INCLUDED
#define WARRAY_WARRAY_ACCESSOR_H_INCLUDED

#include "wassert.h"
#include "warray_types.h"
#include "warray_extents.h"

namespace warp {
namespace detail {

template<typename T, warray_types::size_type NUM_DIMS, typename Tptr=const T*>
class const_array_accessor;

template<typename T, warray_types::size_type NUM_DIMS, typename Tptr=const T*>
struct choose_const_accessor_result_type
{
	typedef const_array_accessor<T,NUM_DIMS,Tptr> type;
};

template<typename T, typename Tptr>
struct choose_const_accessor_result_type<T,0,Tptr>
{
	typedef const T& type;
};

template<typename T, warray_types::size_type NUM_DIMS, warray_types::size_type NUM_INDICES, typename Tptr=const T*>
struct const_accessor_result_type_gen
{
	typedef typename choose_const_accessor_result_type<T,NUM_DIMS-NUM_INDICES,Tptr>::type type;
};

template<typename T, warray_types::size_type NUM_DIMS>
class array_accessor;

template<typename T, warray_types::size_type NUM_DIMS>
struct choose_accessor_result_type
{
	typedef array_accessor<T,NUM_DIMS> type;
};

template<typename T>
struct choose_accessor_result_type<T,0>
{
	typedef T& type;
};

template<typename T, warray_types::size_type NUM_DIMS, warray_types::size_type NUM_INDICES>
struct accessor_result_type_gen
{
	typedef typename choose_accessor_result_type<T,NUM_DIMS-NUM_INDICES>::type type;
};

/*!
	\brief Provides operator[] for constant N-dimensional arrays with arbitrary 
	strides.
*/
template<typename T, warray_types::size_type NUM_DIMS, typename Tptr>
class const_array_accessor
{
	template<warray_types::size_type NUM_INDICES>
	struct const_reference_N
	{
		typedef typename const_accessor_result_type_gen<T,NUM_DIMS,NUM_INDICES,Tptr>::type type;
	};
public:
	typedef warray_types::size_type size_type;
	typedef warray_types::index_type index_type;
	typedef T value_type;
	typedef typename const_reference_N<1>::type const_reference;
	
	template<typename Optr>
	const_array_accessor(const const_array_accessor<T,NUM_DIMS,Optr>& other) :
		m_strides(other.m_strides),
		m_dims(other.m_dims),
		m_origin(other.m_origin)
	{
	}
	
	const_reference operator[](index_type i) const
	{
#ifdef BOUNDS_CHECK
		warp_assert_range(size_type(0),size_type(i),m_dims[0]);
#endif
		return const_reference(
			m_strides+1,
			m_dims+1,
			m_origin+m_strides[0]*i);
	}
	
	template<size_type N> 
	typename const_reference_N<N>::type operator[](const array_indices<N>& i) const
	{
		return access(i);
	}
	
	Tptr c_array() const 
	{
		return m_origin;
	}
	
protected:
	template<size_type N>
	Tptr get_origin(const array_indices<N>& i) const
	{
		Tptr new_origin = m_origin;
		typename array_indices<N>::const_iterator index=i.begin();
		const size_type *stride = m_strides;
		const size_type *dim    = m_dims;
		for(;index!=i.end();++index,++stride,++dim)
		{
#ifdef BOUNDS_CHECK
			warp_assert_range(size_type(0), size_type(*index), *dim);
#endif
			new_origin += (*stride)*(*index);
		}
		return new_origin;
	}
	
	template<size_type N>
	typename const_reference_N<N>::type access(const array_indices<N>& i) const
	{
		return typename const_reference_N<N>::type(
			m_strides+N, 
			m_dims+N, 
			get_origin(i));
	}
	
	typename const_reference_N<NUM_DIMS>::type access(const array_indices<NUM_DIMS>& i) const
	{
		return *get_origin(i);
	}

	const_array_accessor(const size_type *strides, const size_type *dims, Tptr origin) :
		m_strides(strides),
		m_dims(dims),
		m_origin(origin)
	{
	}
	
	template<typename OT, size_type ON>
	friend class arraynd;
	template<typename OT, size_type ON, typename OTptr>
	friend class const_arraynd;
	template<typename OT, size_type ON, typename OTptr>
	friend class const_array_accessor;
	
	const size_type *m_strides;
	const size_type *m_dims;
	Tptr             m_origin;
};

/*!
	\brief Provides operator[] for constant 1-dimensional arrays with arbitrary 
	stride.
*/
template <typename T, typename Tptr>
class const_array_accessor<T,1,Tptr>
{
public:
	typedef warray_types::size_type size_type;
	typedef warray_types::index_type index_type;
	typedef const T& const_reference;
	typedef T value_type;
	
	template<typename Optr>
	const_array_accessor(const const_array_accessor<T,1,Optr>& other) :
		m_stride(other.m_stride),
		m_dim(other.m_dim),
		m_origin(other.m_origin)
	{
	}
	
	const_reference operator[](index_type i) const
	{
#ifdef BOUNDS_CHECK
		warp_assert_range(size_type(0), size_type(i), m_dim);
#endif
		return m_origin[i*m_stride];
	}
	
	const_reference operator[](const array_indices<1>& i) const
	{
#ifdef BOUNDS_CHECK
		warp_assert_range(size_type(0), size_type(i.index(0)), m_dim);
#endif
		return m_origin[i.index(0)*m_stride];
	}
	
	const_array_accessor<T,1,Tptr> operator[](const array_indices<0>& i) const
	{
		return *this;
	}
	
	Tptr c_array() const 
	{
		return m_origin;
	}

protected:
	const_array_accessor(const size_type *strides, const size_type *dims, Tptr origin) :
		m_stride(strides[0]),
		m_dim(dims[0]),
		m_origin(origin)
	{
	}
	
	template<typename OT, size_type ON>
	friend class arraynd;
	template<typename OT, size_type ON, typename OTptr>
	friend class const_arraynd;
	template<typename OT, size_type ON, typename OTptr>
	friend class const_array_accessor;

	size_type   m_stride;
	size_type   m_dim;
	Tptr        m_origin;
};

template <typename T, typename Tptr>
class const_array_accessor<T,0,Tptr>
{
};

/*!
	\brief Provides operator[] for N-dimensional arrays with arbitrary 
	strides.
*/
template<typename T, warray_types::size_type NUM_DIMS>
class array_accessor : public const_array_accessor<T,NUM_DIMS,T*>
{
	typedef const_array_accessor<T,NUM_DIMS,T*> super_type;
	template<warray_types::size_type NUM_INDICES>
	struct reference_N
	{
		typedef typename accessor_result_type_gen<T,NUM_DIMS,NUM_INDICES>::type type;
	};
public:
	typedef typename super_type::size_type        size_type;
	typedef typename super_type::index_type       index_type;
	typedef typename super_type::const_reference  const_reference;
	typedef typename super_type::value_type       value_type;
	typedef typename reference_N<1>::type         reference;
	
	reference operator[](index_type i) const
	{
#ifdef BOUNDS_CHECK
		warp_assert_range(size_type(0), size_type(i), super_type::m_dims[0]);
#endif
		return reference(
			super_type::m_strides+1,
			super_type::m_dims+1,
			super_type::m_origin+super_type::m_strides[0]*i);
	}
	
	template<size_type N> 
	typename reference_N<N>::type operator[](const array_indices<N>& i) const
	{
		return access(i);
	}

protected:
	array_accessor(const size_type *strides, const size_type *dims, T* origin) :
		super_type(strides,dims,origin)
	{
	}
	
	template<size_type N>
	typename reference_N<N>::type access(const array_indices<N>& i) const
	{
		return typename reference_N<N>::type(
			super_type::m_strides+N, 
			super_type::m_dims+N, 
			super_type::get_origin(i));
	}
	
	typename reference_N<NUM_DIMS>::type access(const array_indices<NUM_DIMS>& i) const
	{
		return *get_origin(i);
	}
	
	template<typename OT, size_type ON>
	friend class arraynd;
	template<typename OT, size_type ON>
	friend class array_accessor;
};

/*!
	\brief Provides operator[] for 1-dimensional arrays with arbitrary 
	strides.
*/
template<typename T>
class array_accessor<T,1> : public const_array_accessor<T,1,T*>
{
	typedef const_array_accessor<T,1,T*> super_type;
public:
	typedef typename super_type::size_type size_type;
	typedef typename super_type::index_type index_type;
	typedef typename super_type::const_reference const_reference;
	typedef typename super_type::value_type value_type;
	typedef T& reference;
	
	reference operator[](index_type i) const
	{
#ifdef BOUNDS_CHECK
		warp_assert_range(size_type(0), size_type(i), super_type::m_dim);
#endif
		return super_type::m_origin[i*super_type::m_stride];
	}
	
	reference operator[](const array_indices<1>& i) const
	{
#ifdef BOUNDS_CHECK
		warp_assert_range(size_type(0), size_type(i.index(0)), super_type::m_dim);
#endif
		return super_type::m_origin[i.index(0)*super_type::m_stride];
	}
	
	array_accessor<T,1> operator[](const array_indices<0>& i) const
	{
		return *this;
	}

protected:
	array_accessor(const size_type *strides, const size_type *dims, T* origin) :
		super_type(strides,dims,origin)
	{
	}
	
	template<typename OT, size_type ON>
	friend class arraynd;
	template<typename OT, size_type ON>
	friend class array_accessor;
};

template<typename T>
class array_accessor<T,0>
{
};

} // namespace detail
} // namespace warp

#endif
